﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;

namespace TrackerRealm.Jazz.Client
{
    /// <summary>
    /// 
    /// </summary>
    public class StatesCollection : IEnumerable<jState>, ICollection<jState>
    {
        /// <summary>
        /// 
        /// </summary>
        public static StatesCollection Empty = new StatesCollection(jState.EmptyArray, jState.Empty);
        private jState[] statesArray;
        /// <summary>
        /// A dictionary of state collections indexed by type.
        /// </summary>
        private static Dictionary<Type, StatesCollection> statesCollectionCache = new Dictionary<Type, StatesCollection>();
        /// <summary>
        /// Create a states collection or returns a previousily created collection.
        /// </summary>
        /// <param name="jObjectType"></param>
        /// <returns></returns>
        public static StatesCollection Create(Type jObjectType)
        {
            StatesCollection states;
            if (statesCollectionCache.TryGetValue(jObjectType, out states)) return states;
            List<jState> mList = new List<jState>(8);

            Type t = jObjectType;
            while (t != typeof(jObject))
            {
                //MethodInfo[] ms = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
                foreach (MethodInfo m in t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
                {
                    object[] stateMethods = m.GetCustomAttributes(typeof(StartStateAttribute), true);
                    if (stateMethods.Length > 1)
                        throw new Exception(String.Format("Only a single state Attribute allow for a method. See method '{0}'-{1}", m.Name, jObjectType.FullName));
                    if (stateMethods.Length == 1)
                    {
                        mList.Add(jState.CreateStart((MethodInfo)m));
                        continue;
                    }
                    stateMethods = m.GetCustomAttributes(typeof(FinalStateAttribute), true);
                    if (stateMethods.Length > 1)
                        throw new Exception(String.Format("Only a single state Attribute allow for a method. See method '{0}'-{1}", m.Name, jObjectType.FullName));
                    if (stateMethods.Length == 1)
                    {
                        mList.Add(jState.CreateFinal((MethodInfo)m));
                        continue;
                    }
                    stateMethods = m.GetCustomAttributes(typeof(StateAttribute), true);
                    if (stateMethods.Length > 1)
                        throw new Exception(String.Format("Only a single state Attribute allow for a method. See method '{0}'-{1}", m.Name, jObjectType.FullName));
                    if (stateMethods.Length == 1)
                    {
                        mList.Add(jState.Create((MethodInfo)m));
                    }

                }
                t = t.BaseType;
            }
            jState[] stateArray = new jState[mList.Count];
            int startCount = 0;
            jState startState = jState.Empty;
            foreach (jState s in mList)
            {
                if (s.StateType == StateType.Start)
                {
                    startCount++;
                    startState = s;
                }
            }
            #region Check for Multiple Start States
            if (startCount > 1)
            {
                List<string> list = new List<string>();
                foreach (jState s in mList)
                {
                    if (s.StateType == StateType.Start)
                    {
                        list.Add(s.Name);
                    }
                }
                throw new JazzConfigurationException(ConfigurationErrorType.MultipleStartStateAttributesUsed,
                    string.Format(
                    "Only a single start state is allowed. The following methods were declared start states:'{0}'.  "+
                    "The attribute was used in the class '{1}'",
                    string.Join("', '", list.ToArray()),
                    jObjectType.FullName));
            }
            #endregion
            states = new StatesCollection(mList, startState);
            statesCollectionCache.Add(jObjectType, states);
            return states;
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="states"></param>
        /// <param name="startState"></param>
        private StatesCollection(IEnumerable<jState> states, jState startState)
        {
            this.statesArray = states.ToArray();
            this.StartState = startState;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public jState Find(string name)
        {
            jState state = this.statesArray.Where(s => s.Name == name).SingleOrDefault();
            if (state == null) return jState.Empty;
            return state;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public bool TryFind(string name, out jState state)
        {
            state = this.statesArray.FirstOrDefault(s => s.Name == name);
            if (state == null) return false;
            return true;
        }
        /// <summary>
        /// 
        /// </summary>
        public jState StartState
        {
            get;
            private set;
        }
        #region IEnumerable<jState> Members
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator<jState> GetEnumerator()
        {
            return ((IEnumerable<jState>)this.statesArray).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region ICollection<jRole> Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        public void Add(jState item)
        {
            throw new InvalidOperationException();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Clear()
        {
            throw new InvalidOperationException();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(jState item)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Returns true if the collection contains a State with the specified 'name'.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool Contains(string name)
        {
            return (this.statesArray.Where(s=> s.Name == name).SingleOrDefault() != null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        public void CopyTo(jState[] array, int arrayIndex)
        {
            this.statesArray.CopyTo(array, arrayIndex);
        }
        /// <summary>
        /// 
        /// </summary>
        public int Count
        {
            get { return this.statesArray.Length; }
        }
        /// <summary>
        /// 
        /// </summary>
        public bool IsReadOnly
        {
            get { return true; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(jState item)
        {
            throw new InvalidOperationException();
        }

        #endregion
    }
}
